package Executor;


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.xml.sax.SAXException;

import engine.XPathQueryEngine;

import parser.Node;
import parser.Parser;
import parser.Root;
import simulation.Simulation;

public class XPathMinimizer {
	HashMap<String,String> parentMap;
	HashMap<String,List<String>> ancestorMap ;
	String filePath ;
	String queryResult ;
	Stack<Node> stack ;
	String processedQuery ;
	long executionTimeOriginalQuery ;
	long queryMinimizationTime;
	long executionTimeMinimizedQuery;
	
	public long getExecutionTimeOriginalQuery() {
		return executionTimeOriginalQuery;
	}

	public long getQueryMinimizationTime() {
		return queryMinimizationTime;
	}

	public long getExecutionTimeMinimizedQuery() {
		return executionTimeMinimizedQuery;
	}

	
	
	public String getProcessedQuery() {
		return processedQuery;
	}

	public String getQueryResult() {
		return queryResult;
	}
	
	//constructor.
	public XPathMinimizer(String xmlFile){
		filePath = xmlFile ;
		parentMap = new HashMap<String,String>() ;
		ancestorMap = new HashMap<String,List<String>>() ;
		queryResult = "" ;
		stack = new Stack<Node>();
	}
	public void setConstrains(HashMap<String,String> requiredParent,HashMap<String,List<String>> requiredAncestor)
	{
		parentMap = requiredParent;
		ancestorMap = requiredAncestor;
	}
	
	// main function to execute a query passed by GUI
	public void executeQuery(String query,Boolean performMinimize){
		queryResult = "" ;
		processedQuery = "" ;
		executionTimeOriginalQuery = 0;
		queryMinimizationTime = 0;
		executionTimeMinimizedQuery = 0;
		if (performMinimize) {
			long startMinimize = System.currentTimeMillis();
			List<Root> queryTree = Parser.parse(query);
			if (!parentMap.isEmpty() || !ancestorMap.isEmpty())
				reduceQuery(queryTree);
			Simulation sim = new Simulation();
			for (Root root : queryTree) {
				sim.simulate(root, ancestorMap);
			}
			Minimization minimize = new Minimization();
			minimize.minimize(queryTree);
		    long endMinimize = System.currentTimeMillis();
		    queryMinimizationTime = endMinimize - startMinimize ;
			try {
				XPathQueryEngine queryExecutor = new XPathQueryEngine(filePath);

				for (Root queryRoot : queryTree) {
					processedQuery = queryRoot.getQuery();
					//System.out.println(processedQuery);
					try {
						long start = System.currentTimeMillis();
						Object result = queryExecutor.query(processedQuery);
						long end = System.currentTimeMillis();
						executionTimeMinimizedQuery += end-start ;
						queryResult += queryExecutor.printResult(result);
					} catch (XPathExpressionException e) {
						queryResult = "invalid query.";
						
					}
					queryResult += "\n";
				}
			} catch (ParserConfigurationException e) {
				queryResult = "Problem with Java Parser Configuration.\n";
				queryResult += e.getMessage();
			} catch (SAXException e) {
				queryResult = "Problem encountered when parsing using SAX.\n";
				queryResult += e.getMessage();
			} catch (IOException e) {
				queryResult = "Problem reading file. \n";
				queryResult += e.getMessage();
			}
		} else {
			try {
				XPathQueryEngine queryExecutor = new XPathQueryEngine(filePath);
				long start = System.currentTimeMillis();
				Object result = queryExecutor.query(query);
				long end = System.currentTimeMillis();
				executionTimeOriginalQuery = end - start ;
				queryResult += queryExecutor.printResult(result);
			} catch (ParserConfigurationException e) {
				queryResult = "Problem with Java Parser Configuration.\n";
				queryResult += e.getMessage();
			} catch (SAXException e) {
				queryResult = "Problem encountered when parsing using SAX.\n";
				queryResult += e.getMessage();
			} catch (IOException e) {
				queryResult = "Problem reading file. \n";
				queryResult += e.getMessage();
			} catch (XPathExpressionException e) {
				queryResult = "invalid query.";
			}
		}
		
		
	}
	
	private void reduceQuery(List<Root> queryTree) {
		for (Root root : queryTree){
			//reduceNode((Node) root);
			reduceNodeStack(root);
		}
	}
	private void reduceNodeStack(Node node) {
		stack.push(node);
		List<Node> pushedNodes = new ArrayList<Node>();
		while (!stack.isEmpty()) {
			while (!pushedNodes.contains(node)&&
					(!node.getChild().isEmpty() || !node.getDecendant().isEmpty())) {
				if (!node.getChild().isEmpty()) {
					for (Node childNode : node.getChild()) {
						stack.push(childNode);
						
					}
				} 
				if (!node.getDecendant().isEmpty()) {
					for (Node decedantNode : node.getDecendant()) {
						stack.push(decedantNode);
						
					}
				}
				pushedNodes.add(node);
				node = stack.peek();
			}
			if (!node.isOutput() && node.isLeaf()) {
				Node parent = node.getParent();
				if (parent != null
						&& parentMap.get(node.getType()) != null
						&& parentMap.get(node.getType()).equals(
								parent.getType())) {
					parent.removeChild(node);

				}

				Node ancestor = node.getAncestor();
				if (ancestor != null) {
					if (ancestorMap.get(node.getType()) != null) {
						for (String ancestorType : ancestorMap.get(node
								.getType())) {
							if (ancestor.getType().equals(ancestorType)) {
								ancestor.removeDecendant(node);
							}
						}
					}
					if (parentMap.get(node.getType()) != null){
						if (ancestor.getType().equals(parentMap.get(node.getType())))
							ancestor.removeDecendant(node);
					}
					
				}
			}
			stack.pop();
			if (!stack.isEmpty()) node = stack.peek();
		}
	}
	/* recursive way to reduce query. 
	private Boolean reduceNode(Node node) {
		if (node.isLeaf() && !node.isOutput()){
			Node parent = node.getParent() ;
			if (parent !=null && parentMap.get(node.getType()) != null 
					&& parentMap.get(node.getType()).equals(parent.getType())){
				parent.removeChild(node) ;
				return true ;
			}
			
			Node ancestor = node.getAncestor();
			if (ancestor !=null){
				if (ancestorMap.get(node.getType())==null) return false ;
				for (String ancestorType : ancestorMap.get(node.getType())){
					if (ancestor.getType().equals(ancestorType)){
						ancestor.removeDecendant(node);
						return true;
					}
				}
				
			}				
			return false ;	
		}
		else{
			if (!node.getChild().isEmpty()){
				List<Node> childList = node.getChild();
				for (int i=0;i<childList.size();i++){
					Node child = childList.get(i);
					if (reduceNode(child)) i--;
				}
					
			}
			if (!node.getDecendant().isEmpty()){
				List<Node> decendantList = node.getDecendant() ;
				for (int i=0;i<decendantList.size();i++){
					Node decedant = decendantList.get(i);
					if (reduceNode(decedant)) i-- ;
				}
			}
			if (node.isLeaf()) reduceNode(node);
			return false ;
		}
					
	}
	*/
	
	
}
